package aspectjutil.eventmanager;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aspectjutil.adviseutil.Util;
import aspectjutil.eventmanager.label.EventListener;

/**
 * Inspired by {@link http://www.aspectprogrammer.org/blogs/adrian/2005/03/event_driven_ar.html}.
 * It may be worthy to research whether it is possible to implement this by extending AbstractProcessor. 
 * @author ay
 */
@Aspect( "pertypewithin( @aspectjutil.eventmanager.label.EventListener *)")
public class EventListenerInstanceTracker {
	protected Logger logger = LoggerFactory.getLogger( this.getClass());
	
	protected Map<Object, WeakReference<Long>> listenerMap 
	= Collections.synchronizedMap( new WeakHashMap<Object, WeakReference<Long>>());
		// To be at safe side, use WeakReference for value.
	
	@Pointcut( "initialization( new(..)) && this( obj)")
	public static void eventListenerConstructorPointcut( Object obj) {}
	
	@AfterReturning( pointcut="eventListenerConstructorPointcut( obj)", argNames="obj")
	public void afterEventListenerConstructor( Object obj) {
		WeakReference<Long> longWeakReference = new WeakReference<Long>( Thread.currentThread().getId());
		synchronized( listenerMap) {
			listenerMap.put( obj, longWeakReference);
		} // synchronized
	}
	
	protected void dumpListenerInstanceMap( LinkedHashMap<Object, Long> listenerInstanceMap) {
		if ( logger.isDebugEnabled()) {
			logger.debug( 
					String.format(
							"Listers of event: %n%1$s",
							listenerInstanceMap.toString()
							)
					);
		}
	}
	
	/**
	 * @return Set of instance objects what includes subclasses.
	 */
	public Map<Object, Long> getListeners() {
		LinkedHashMap<Object, Long> instanceMap = new LinkedHashMap<Object, Long>();
		
		List<Object> listenerObjList = new ArrayList<Object>( listenerMap.keySet());
		for( Object listenerObj : listenerObjList) {
			if ( listenerObj == null) continue; // for
			Long threadIdLong = listenerMap.get( listenerObj).get();
			if ( threadIdLong == null) continue; // for
			instanceMap.put( listenerObj, threadIdLong);
		} // for
		
		dumpListenerInstanceMap( instanceMap);
		
		return instanceMap;
	}
	
	public Map<Object, Long> getListeners( long threadId) {
		LinkedHashMap<Object, Long> instanceMap = new LinkedHashMap<Object, Long>();
		
		List<Object> listenerObjList = new ArrayList<Object>( listenerMap.keySet());
		for( Object listenerObj : listenerObjList) {
			if ( listenerObj == null) continue; // for
			Long threadIdLong = listenerMap.get( listenerObj).get();
			if ( threadIdLong == null) continue; // for
			
			if ( threadId == threadIdLong.longValue()) 
			{
				instanceMap.put( listenerObj, threadIdLong);
			}
		} // for
		
		dumpListenerInstanceMap( instanceMap);
		
		return instanceMap;
	}
	
	public Map<Object, Long> getListeners( Class<?> listenerClass) {
		LinkedHashMap<Object, Long> instanceMap = new LinkedHashMap<Object, Long>();
		
		List<Object> listenerObjList = new ArrayList<Object>( listenerMap.keySet());
		for( Object listenerObj : listenerObjList) {
			if ( listenerObj == null) continue; // for
			Long threadIdLong = listenerMap.get( listenerObj).get();
			if ( threadIdLong == null) continue; // for
			
			if ( listenerClass.equals( listenerObj.getClass())) {
				instanceMap.put( 
						listenerObj, 
						threadIdLong
						);
			}
		} // for
		
		dumpListenerInstanceMap( instanceMap);
		
		return instanceMap;
	}
	
	public Map<Object, Long> getListeners( Class<?> listenerClass, long threadId) {
		LinkedHashMap<Object, Long> instanceMap = new LinkedHashMap<Object, Long>();
		
		List<Object> listenerObjList = new ArrayList<Object>( listenerMap.keySet());
		for( Object listenerObj : listenerObjList) {
			if ( listenerObj == null) continue; // for
			Long threadIdLong = listenerMap.get( listenerObj).get();
			if ( threadIdLong == null) continue; // for
			
			if ( 
					listenerClass.equals( listenerObj.getClass()) 
					&& threadId == threadIdLong.longValue()
					) 
			{
				instanceMap.put( 
						listenerObj, 
						threadIdLong
						);
			}
		} // for
		
		dumpListenerInstanceMap( instanceMap);
		
		return instanceMap;
	}
	
	
	public void clear() {
		synchronized( listenerMap) {
			listenerMap.clear();
		} // synchronized
	}
}
